Sieci komputerowe — ćwiczenia 2
Temat zajęć: Programowanie gniazd BSD c.d.
Literatura:
- R. Stevens, "Programowanie zastosowań sieciowych w systemie Unix"
- A. Jones, J. Ohlund, "Programowanie sieciowe Microsoft Windows"
- C. Petzold, "Programowanie Windows"
- M. Gabassi, B. Dupouy, "Przetwarzanie rozproszone w systemie Unix"
- E. Harold, "Java: programowanie sieciowe"
- R. Stevens, "Biblia TCP/IP" (tom 1 - Protokoły i tom 2 - Implementacje)
- C. Hunt, "TCP/IP - administracja sieci"
- V. Toth, "Programowanie Windows 98/NT - księga eksperta"
- Dokumenty RFC wersja on-line
Protokół UDP
W przypadku korzystania z protokołu UDP miejsce funkcji send i recv zajmują funkcje
- int sendto(int socket, void *buffer, size_t nbytes, int flags, struct sockaddr *receiver, int addrlen) (prototyp w sys/socket.h)
wysyła nbytes bajtów z bufora wskazywanego przez buffer za pomocą gniazda socket pod adres wskazany przez receiver (addrlen jest wielkością struktury sockaddr). Zwraca błąd (-1) praktycznie tylko w przypadku, gdy socket jest nieprawidłowy (np. nieprzydzielony) lub adres nie jest prawidłowo wypełniony. Nie ma kontroli błędów transmisji. Parametr flags ma wartość 0.
- int recvfrom(int socket, void *buffer, size_t nbytes, int flags, struct sockaddr *sender, int *addrlen) (prototyp w sys/socket.h)
funkcja blokująca - odbiera co najwyżej nbytes bajtów za pośrednictwem gniazda socket i umieszcza je w buforze buffer. Adres nadawcy umieszczany jest w strukturze wskazywanej przez sender (addrlen to wskaźnik do długości struktury). flags, podobnie jak poprzednio, ma wartość 0.Przykład 1
Przykład realizuje te same funkcje, co Przykład 5 z Ćwiczeń 1 (przesłanie wiersza tekstu), jednak korzysta z protokołu UDP.
Plik c2p1a.c pobierz#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
char buf[512];
int main(int argc, char **argv)
{
struct sockaddr_in myaddr, endpoint;
int sdsocket, r;
socklen_t addrlen;
unsigned int port;
printf("Na ktorym porcie mam sluchac? : ");
scanf("%u", &port);
if ((sdsocket = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
printf("socket() nie powiodl sie\n");
return 1;
}
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(port);
myaddr.sin_addr.s_addr = INADDR_ANY;
if (
bind(sdsocket,
(struct sockaddr*) &myaddr,
sizeof(struct sockaddr_in)) < 0) {
printf("bind() nie powiodl sie\n");
return 1;
}
addrlen = sizeof(struct sockaddr_in);
while (1) { /* nieskonczona petla */
memset(buf, 0, 512);
r = recvfrom(sdsocket,
buf,
512,
0,
(struct sockaddr*) &endpoint,
&addrlen);
printf("Wiadomosc od %s: %s\n",
inet_ntoa(endpoint.sin_addr),
buf);
}
return 0;
}
Plik c2p1b.c pobierz#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
char buf[512];
int main(int argc, char **argv)
{
struct sockaddr_in adr;
int gniazdo, r;
unsigned int port;
char abcd[512];
printf("Podaj adres IP odbiorcy: ");
scanf("%s", abcd);
printf("Podaj numer portu odbiorcy: ");
scanf("%u", &port);
gniazdo = socket(AF_INET, SOCK_DGRAM, 0);
adr.sin_family = AF_INET;
adr.sin_port = htons(port);
adr.sin_addr.s_addr = inet_addr(abcd);
printf("Podaj wiadomosc: ");
fflush(stdout); fgetc(stdin);
fgets(buf, 512, stdin);
r = sendto(gniazdo,
buf,
512,
0,
(struct sockaddr*) &adr,
sizeof(adr));
if (r != 512) printf("sendto() nie powiodl sie\n");
else printf("Wiadomosc wyslana.\n");
close(gniazdo);
return 0;
}Zadanie 1
Podobnie jak w przypadku Przykładu 5 z Ćwiczeń 1, proszę zmodyfikować kod przykładu tak, aby po pierwsze użytkownik mógł podawać nazwy domenowe zamiast adresów IP, a po drugie aby możliwe było przesłanie dowolnej liczby komunikatów w jednej sesji (jednym uruchomieniu programu klienckiego).Przykład 2
Poniższy przykład mierzy średnią wydajność protokołu TCP przy przesyłaniu bloków o długości 10000 bajtów w obie strony. Przykład działa w ten sposób, że do każdego przesłania bloku nawiązywane jest osobne połączenie. Mierzymy więc czas, jaki zabiera socket, connect, send 10000 bajtów, recv 10000 bajtów i close. Do mierzenia czasu wykorzystana została funkcja int gettimeofday(struct timeval *t, struct timezone *z) (za z podstawiamy NULL, bo strefa czasowa nas akurat nie obchodzi). Funkcja ta wypełnia strukturę wskazywaną przez t danymi o bieżącym czasie z dokładnością do mikrosekundy. Struktura timeval (zarówno struktura, jak i funkcja gettimeofday zdefiniowane są w sys/time.h) zawiera dwa pola typu long: tv_sec i tv_usec. Określają one czas (w sekundach i mikrosekundach), jaki minął od północy dnia 1 stycznia 1970r. Wystarczy więc wywołać gettimeofday przed i po badanym bloku instrukcji, a następnie odjąć odp. wielkości (pamiętając o skali) aby otrzymać przedział czasowy. W naszym przykładzie powtarzamy proces przesyłania 100 razy (średni czas pojedynczej transmisji klient-serwer-klient uzyskamy dzieląc otrzymany wynik przez 100).
UWAGA: Tym razem program nie pyta o adres i numer portu w sposób interaktywny. W przypadku serwera (c2p2a.c) należy jako parametr w wierszu poleceń podać numer portu, a w przypadku klienta należy podać jako parametry kolejno nazwe hosta serwera i jego numer portu.
Plik c2p2a.c pobierz#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
/* rozmiar bufora */
#define BUFFER_SIZE 10000
/* liczba powtorzen */
#define ATTEMPTS 100
char buf[BUFFER_SIZE];
/*
argv[1] - numer portu
*/
int main(int argc, char **argv)
{
struct sockaddr_in myaddr, endpoint;
int sdsocket, sdconnection, addrlen, received;
if (argc < 2) {
printf("podaj numer portu jako parametr\n");
return 1;
}
sdsocket = socket(AF_INET, SOCK_STREAM, 0);
addrlen = sizeof(struct sockaddr_in);
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(atoi(argv[1]));
myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(sdsocket,(struct sockaddr*) &myaddr,addrlen) < 0) {
printf("bind() nie powiodl sie\n");
return 1;
}
if (listen(sdsocket, 10) < 0) {
printf("listen() nie powiodl sie\n");
return 1;
}
while ((sdconnection =
accept(sdsocket,
(struct sockaddr*) &endpoint,
&addrlen)) >= 0)
{
received = 0;
/* odbior moze odbywac sie w mniejszych
segmentach */
while (received < BUFFER_SIZE)
{
received += recv(sdconnection,
buf+received,
BUFFER_SIZE-received,
0);
}
send(sdconnection, buf, BUFFER_SIZE, 0);
close(sdconnection);
}
close(sdsocket);
return 0;
}
Plik c2p2b.c pobierz#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/time.h>
#define BUFFER_SIZE 10000
#define ATTEMPTS 100
char buf[BUFFER_SIZE];
/*
argv[1] - nazwa hosta
argv[2] - numer portu
*/
int main(int argc, char **argv)
{
struct sockaddr_in endpoint;
int sdsocket, addrlen, i, received;
struct hostent *he;
struct timeval time_b, time_e;
if (argc<3) {
printf("podaj nazwe hosta i numer portu jako parametry\n");
return 1;
}
he = gethostbyname(argv[1]);
if (he == NULL) {
printf("Nieznany host: %s\n",argv[1]);
return 0;
}
endpoint.sin_family = AF_INET;
endpoint.sin_port = htons(atoi(argv[2]));
endpoint.sin_addr = *(struct in_addr*) he->h_addr;
addrlen = sizeof(struct sockaddr_in);
gettimeofday(&time_b, NULL);
for (i=0; i<ATTEMPTS; i++) {
if ((sdsocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
printf("socket() %d nie powiodl sie\n", i);
return 1;
}
if (connect(sdsocket,(struct sockaddr*) &endpoint, addrlen) < 0) {
printf("connect() %d nie powiodl sie\n", i);
return 0;
}
send(sdsocket, buf, BUFFER_SIZE, 0);
received = 0;
while (received < BUFFER_SIZE)
{
received += recv(sdsocket,
buf+received,
BUFFER_SIZE-received,
0);
}
close(sdsocket);
}
gettimeofday(&time_e, NULL);
printf("czas: %.6f s\n",
(((double) (time_e.tv_sec - time_b.tv_sec) * 1000000) +
((double) (time_e.tv_usec - time_b.tv_usec)))
/ (1000000.0 * ATTEMPTS));
return 0;
}Zadanie 2
Wysłać, korzystając z protokołu UDP (IPv4) na adres 150.254.77.101, port (zostanie podany na tablicy) z lokalnego komputera pakiet zawierający numer indeksu przesłany jako ciąg znaków ASCII bez spacji zakończony znakiem przejścia do nowej linii i ten sam numer, zapisany jako czterobajtowa liczba bez znaku, zapisana w kolejności sieciowej. Ewentualną odpowiedź należy zapisać jako potwierdzenie. Przykład dla numeru 123456, podane wartości szesnastkowe kolejnych bajtów (podpowiedź: do zapisu w postaci kodów ASCII można wykorzystać funcję sprintf()):31 32 33 34 35 36 0A 00 01 E2 40Zadanie 3 (domowe)
W oparciu o kod z Przykładu 2 zaimplementować następujące przypadki:Przeimplementować należy zarówno stronę klienta, jak i stronę serwera.
- Przesyłanie bloków 10000-bajtowych za pomocą protokołu UDP z pomiarem czasu. Program powinien działać wg schematu pomiar czasu, socket, 100 razy sendto i recvfrom, close, pomiar czasu, podanie czasu podzielonego przez 100.
- Przesyłanie bloków 10000-bajtowych za pomocą protokołu TCP w jednej sesji z pomiarem czasu. Program powinien działać wg schematu pomiar czasu, socket, connect, 100 razy send i recv, close, pomiar czasu, podanie czasu podzielonego przez 100.
Program proszę uruchamiać na dwóch komputerach w laboratoriach wydziału aby uzyskane wyniki faktycznie uwzględniały przesyłanie danych przez sieć.
Wyniki wszystkich trzech (włączając w to Przykład 2) pomiarów proszę przesłać e-mailem na adres marcing[at]wmi[dot]amu[dot]edu[dot]pl, umieszczając w tytule listu ciąg DSIKLI0 1CX, gdzie X to numer grupy (ułatwi mi to sortowanie poczty).
Na wyniki czekam do niedzieli, 16 marca 2014r.
Problem do przemyślenia
Załóżmy, że pewna usługa sieciowa ma być dostępna zarówno przez TCP, jak i UDP. Powinna więc nasłuchiwać na dwóch gniazdach (oznaczmy je przez gnTCP i gnUDP) jednocześnie, ponieważ nie wiadomo, którą drogą przyjdzie żądanie wykonania usługi od potencjalnego klienta.
Z jednej strony więc nasz program powinien wykonać sekwencję
gnTCP = socket(...)
bind(gnTCP,...)
listen(gnTCP,...)
gnTCP_klient = accept(gnTCP,...) BLOKADA
send / recv na gnTCP_klient
close(gnTCP_klient)
ew. powrót do accept
a z drugiej, w tym samym czasie, sekwencję
gnUDP = socket(...)
bind(gnUDP,...)
recvfrom(gnUDP,...) BLOKADA
sendto / recvfrom na gnUDP
close(gnUDP)
ew. powrót do pierwszego recvfrom
Problem polega na tym, że jeśli wykonamy accept na gnieździe TCP, program traci sterowanie do momentu nawiązania połączenia przez klienta TCP, zatem nie mamy szansy wykonać recvfrom na gnieździe UDP, aby zapewnić nasłuch również na tym protokole. Z drugiej strony, jeśli najpierw wykonamy recvfrom na gnieździe UDP, program również straci sterowanie do momentu odebrania pierwszego datagramu i nie będzie możliwe wykonanie accept na gnieździe TCP.
Możliwe podejścia do rozwiązania problemu:Proszę przeczytać informacje na temat funkcji select znajdujące się w man pages systemu (sekcja 2).
- zaimplementować program wielowątkowy - jeden wątek blokowany jest na nasłuchu TCP, a drugi na nasłuchu UDP - jest to metoda dość kosztowna programistycznie (przynajmniej w C) i wprowadza do aplikacji kolejne problemy związane z programowaniem współbieżnym,
- wykorzystać funkcję select - jest to rozwiązanie zdecydowanie prostsze i całkowicie rozwiązuje przedstawiony problem.
Przykłady stosowania funkcji select przeanalizujemy na następnych zajęciach.
Problem do przemyślenia 2
Wszystkie dotychczasowe przykłady wykorzystują protokół IPv4. Jak zmienić je, by stosowały IPv6?
Begin main navigation